<script setup lang="ts" name="UploadCropperImage">
import { propTypes } from '@/utils/propTypes'
import { FileApi } from '@/api/infra/resource/library'
import Cropper from 'cropperjs'
import { dataURLtoBlob } from '@/utils/file/helper'
import { storeToRefs } from 'pinia'
import { useAppStoreWithOut } from '@/store/modules/app'
import type { UploadProps, UploadRawFile, UploadUserFile } from 'element-plus'
import { getAcceptSize, getMimetype, getFileExt, AcceptSizeType, getImageDataByFile, getImageSizeByFile, getAcceptSizeByAccept, getExtMimetypeConfig } from '@/utils/file/helper'
import { MimetypeType } from '@/utils/file/type'
import { isArray } from '@/utils/is'
interface UploadResourceFile extends UploadUserFile {
    mimetype: string,
    ext: string | undefined,
    duration?: number
}

type CropendResult = {
  imgBase64: string;
  imgInfo: Cropper.Data;
}

const appStore = useAppStoreWithOut()
const { systemConfig } = storeToRefs(appStore)

const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗

const props = defineProps({
  fileList: {
    type: Array as import('vue').PropType<UploadResourceFile[]>,
    default: []
  },
  mimetype: {
    type: [String, Array] as import('vue').PropType<MimetypeType>,
    default: 'image'
  }, // 用于根据mimetype类型，支持上传默认支持的扩展名文件
  accept: propTypes.string.def(''), // 用于支持上传指定扩展名文件
  limit: propTypes.number.def(undefined), // 数量限制 undefined 不限制
  drag: propTypes.bool.def(false), // 拖拽上传
  disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传（默认为 false）
  width: propTypes.number.def(320), // 建议上传图片宽度
  height: propTypes.number.def(180), // 建议上传图片高度
  aspectRatioEnable: propTypes.bool.def(false) // 是否禁用保持宽高比切换事件
})

const visible = ref(false) // 弹窗的是否展示
const formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用
const cropper = ref<Cropper>()
const currentOperate = ref('') // 当前操作
const scaleX = ref(1)
const scaleY = ref(1)
const isInit = ref(true)
const isCropper = ref(false) // 裁剪状态
const isKeepAspectRatio = ref(true) // 保持宽高比
const aspectRatio = ref(Math.trunc(props.width / props.height * 100) / 100) // 宽高比率
// 图片裁剪相关参数
const options = ref<Cropper.Options>({
  viewMode: 1,
  dragMode: 'move', // 拖拽图片的模式 crop: 形成新的裁剪框|move:图片可移动|none: 什么也没有
  initialAspectRatio: NaN, // 定义裁剪框的初始宽高比。默认和图片容器的宽高比相同。 只有在 aspectRatio 的值为 NaN时才可以设置。
  aspectRatio: Math.trunc(props.width / props.height * 100) / 100, // 定义裁剪框的固定宽高比。默认是自由比例
  minContainerWidth: 200, // 设置裁剪容器的最小宽度
  minContainerHeight: 100, // 设置裁剪容器的最小高度
  minCanvasWidth: 0, // 设置图片容器的最小宽度
  minCanvasHeight: 0, // 设置图片容器的最小高度
  minCropBoxWidth: 0, // 设置裁剪框的最小宽度 相对于页面，而非图片
  minCropBoxHeight: 0 // 设置裁剪框的最小高度 相对于页面，而非图片
})

let filename = ''
// 原始图片资源
const originalSource = ref({
  src: '',
  width: props.width,
  height: props.height
})
// 裁剪调整资源
const cropperSource = ref({
  src: '',
  width: props.width,
  height: props.height
})
// 裁剪框改变未确认状态的资源
const cropperChangeSource = ref('')
// 裁剪确认资源
const cropperSaveSource = ref({
  src: '',
  width: props.width,
  height: props.height
})
const config = ref<AcceptSizeType>() // 系统资源配置
const mimetypeConfig = ref<MimetypeType>('') // 资源类型
const acceptConfig = ref('') // 允许的资源扩展名

/** 打开弹窗 */
const open = async () => {
  visible.value = true
  resetForm()
  cropperSource.value.width = props.width
  cropperSource.value.height = props.height
}
defineExpose({ open }) // 提供 open 方法，用于打开弹窗

/** 提交表单 */
const emits = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调

watch(
  () => props.mimetype,
  async (v) => {
    if (!props.accept){
      config.value = await getAcceptSize(v as any)
      mimetypeConfig.value = v
      acceptConfig.value = config.value.accept
    }
  },
  { immediate: true }
)

watch(
  () => props.accept,
  async (v) => {
    if (v) {
      const realConfig = getExtMimetypeConfig(v)
      config.value = await getAcceptSizeByAccept(v)
      mimetypeConfig.value = realConfig.mimetype
      acceptConfig.value = v
    }
  },
  { immediate: true }
)

const submitForm = async () => {
  // 校验表单
  const target = Math.trunc(props.width / props.height * 10) / 10
  const real = Math.trunc(cropperSaveSource.value.width / cropperSaveSource.value.height * 10) / 10
  if (target !== real){
    message.error(t('cropper', 'sizeError'))
    return false
  }
  // 提交请求
  formLoading.value = true
  message.success(t('upload', 'waiting'))
  try {
    const blob: any = dataURLtoBlob(cropperSaveSource.value.src)
    const file = new File([blob], filename)
    const res = await FileApi.upload({ file })
    // message.success(t('upload', 'success'))
    visible.value = false
    // 发送操作成功的事件
    emits('success', res.data)
  } finally {
    formLoading.value = false
  }
}

/** 重置表单 */
const resetForm = () => {
  isInit.value = true
  currentOperate.value = ''
  scaleX.value = 1
  scaleY.value = 1
  isCropper.value = false
  originalSource.value = {
    src: '',
    width: props.width,
    height: props.height
  }
  cropperSource.value = {
    src: '',
    width: props.width,
    height: props.height
  }
  cropperSaveSource.value = {
    src: '',
    width: props.width,
    height: props.height
  }
  cropperChangeSource.value = ''
}

// 宽度设置改变触发事件
const widthChangeHandler = (v) => {
  if (cropper.value) {
    const imgInfo = cropper.value.getData()
    if (!isCropper.value) {
      isCropper.value = true
      cropper.value.crop()
    }
    imgInfo.width = v
    if (isKeepAspectRatio.value) {
      imgInfo.height = v / aspectRatio.value
    } else {
      imgInfo.height = cropperSource.value.height
    }
    cropper.value.setData(imgInfo)
  }
}

// 高度设置改变触发事件
const heightChangeHandler = (v) => {
  if (cropper.value) {
    const imgInfo = cropper.value.getData()
    if (!isCropper.value) {
      isCropper.value = true
      cropper.value.crop()
    }
    imgInfo.height = v
    if (isKeepAspectRatio.value) {
      imgInfo.width = v * aspectRatio.value
    } else {
      imgInfo.width = cropperSource.value.width
    }
    cropper.value.setData(imgInfo)
  }
}

// 保持宽高比切换事件
const changeAspectRatioHandler = (v) => {
  if (cropper.value) {
    const cropBoxInfo = cropper.value.getCropBoxData()
    if (v) {
      const imgInfo = cropper.value.getData()
      if (Math.round(imgInfo.width) === 0) {
        cropper.value.setAspectRatio(Math.trunc(props.width! / props.height! * 100) / 100)
        aspectRatio.value = props.width! / props.height!
      } else {
        aspectRatio.value = Math.trunc(Math.round(imgInfo.width) / Math.round(imgInfo.height) * 100) / 100
        cropper.value.setAspectRatio(Math.trunc(Math.round(imgInfo.width) / Math.round(imgInfo.height) * 100) / 100)
        cropper.value.setCropBoxData(cropBoxInfo)
      }
    } else {
      aspectRatio.value = NaN
      cropper.value.setAspectRatio(NaN)
      cropper.value.setCropBoxData(cropBoxInfo)
    }
  }
}

// 裁剪框发生变化触发事件
const handleCropper = ({ imgBase64, imgInfo }: CropendResult) => {
  if (!cropperSource.value.src) return
  cropperChangeSource.value = imgBase64
  isInit.value = false
  if (isCropper.value) {
    cropperSource.value.width = Math.round(imgInfo.width)
    cropperSource.value.height = Math.round(imgInfo.height)
  } else {
    if (imgInfo.width === 0) {
      if (currentOperate.value === 'reset') {
        isInit.value = true
        cropperSource.value.width = originalSource.value.width
        cropperSource.value.height = originalSource.value.height
      } else {
        cropperSource.value.width = cropperSaveSource.value.width
        cropperSource.value.height = cropperSaveSource.value.height
      }
    } else {
      if (cropperSaveSource.value.src === '') {
        cropperSource.value.width = Math.round(imgInfo.width)
        cropperSource.value.height = Math.round(imgInfo.height)
        isCropper.value = true
        if (cropper.value) {
          cropper.value.crop()
        }
      } else {
        if (cropper.value) {
          cropper.value.replace('')
          cropper.value.clear()
        }
      }
    }
  }
}

// 裁剪框确认事件
const saveCropper = () => {
  cropperSaveSource.value.width = cropperSource.value.width
  cropperSaveSource.value.height = cropperSource.value.height
  cropperSaveSource.value.src = cropperChangeSource.value
  isCropper.value = false
  currentOperate.value = ''
  cropperSource.value.src = cropperChangeSource.value
  cropperChangeSource.value = ''
  if (cropper.value) {
    cropper.value.replace(cropperSaveSource.value.src)
    cropper.value.clear()
  }
}

// 裁剪实例完成触发事件
const handleReady = (cropperInstance: Cropper) => {
  cropper.value = cropperInstance
}

// 工具条点击事件
const handlerToolbar = (type: string, event: string, arg?: number) => {
  if (!cropperSource.value.src) return
  if (cropperSource.value.src) {
    currentOperate.value = type
    if (event === 'scaleX') {
      scaleX.value = arg = scaleX.value === -1 ? 1 : -1
    }
    if (event === 'scaleY') {
      scaleY.value = arg = scaleY.value === -1 ? 1 : -1
    }
    cropper?.value?.[event]?.(arg)
    isInit.value = false
    if (event === 'reset') {
      isInit.value = true
      isCropper.value = false
      cropperSource.value.width = originalSource.value.width
      cropperSource.value.height = originalSource.value.height
      cropperSource.value.src = originalSource.value.src
      cropperSaveSource.value.width = originalSource.value.width
      cropperSaveSource.value.height = originalSource.value.height
      cropperSaveSource.value.src = ''
      cropperChangeSource.value = ''
      if (cropper.value) {
        cropper.value.replace(originalSource.value.src)
        if (isKeepAspectRatio.value) {
          if (props.width && props.height) {
            cropper.value.setAspectRatio(Math.trunc(props.width! / props.height! * 100) / 100)
          }
        } else {
          cropper.value.setAspectRatio(NaN)
        }
        cropper.value.clear()
      }
    } else {
      isInit.value = false
      isCropper.value = true
      if (cropper.value) {
        cropper.value.crop()
      }
    }
  }
}

// 裁剪切换事件
const handlerCopper = () => {
  if (cropperSource.value.src === '') return false
  isCropper.value = !isCropper.value
  if (isCropper.value) {
    if (cropper.value) {
      cropper.value.crop()
    }
  } else {
    if (cropper.value) {
      cropper.value.replace('')
      cropper.value.clear()
    }
  }
}

// 文件上传之前判断
const beforeUpload: UploadProps['beforeUpload'] = async (file: UploadRawFile) => {
  if (props.fileList.length >= props.limit && props.limit) {
    message.error(t('upload', 'limit', [props.limit]))
    return false
  }
  let fileExtension = getFileExt(file.name)
  let fileMimetype = getMimetype(file.name)
  let fileConfig = config.value
  if (isArray(mimetypeConfig.value) && config.value?.config) {
    fileConfig = config.value?.config[fileMimetype]
  }
  const isAcceptFile = acceptConfig.value.split(',').some((ext: string) => {
    if (ext.indexOf(fileMimetype)) return true
    return !!(fileExtension && ext.indexOf(fileExtension) > -1)
  })
  if (!isAcceptFile) {
    message.error(t('upload', 'accept', [acceptConfig.value, t('upload', fileMimetype)]))
    return false
  }
  const isAllowSize = fileConfig?.size === 0 || file.size < (fileConfig?.size as number)
  if (!isAllowSize) {
    message.error(t('upload', 'size', [fileConfig?.sizeText]))
    return false
  }
  filename = file.name
  try {
    cropperSource.value.src = originalSource.value.src = cropperSaveSource.value.src = await getImageDataByFile(file) as string
    const size: any = await getImageSizeByFile(file)
    cropperSource.value.width = originalSource.value.width = cropperSaveSource.value.width = size.width
    cropperSource.value.height = originalSource.value.height = cropperSaveSource.value.height = size.height
  } catch {}
  return false
}
</script>

<template>
  <Dialog v-model="visible" :title="t('cropper', 'uploadImage')" append-to-body width="800px">
    <div v-loading="formLoading && systemConfig.loadingType === 'module'" class="cropper-wrap flex-1">
      <div class="cropper-content flex-1">
        <el-form class="flex mb-12px" inline>
          <ElFormItem :label="t('infra.resource.library.file.edit.image', 'width')">
            <ElInputNumber v-model="cropperSource.width" :disabled="!isCropper" :min="0" :max="originalSource.width" @change="widthChangeHandler" />
          </ElFormItem>
          <ElFormItem class="label-text">
            <p class="m-0">px</p>
          </ElFormItem>
          <ElFormItem :label="t('infra.resource.library.file.edit.image', 'height')" class="!ml-18px">
            <ElInputNumber v-model="cropperSource.height" :disabled="!isCropper" type="number" :min="0" :max="originalSource.height" @change="heightChangeHandler" />
          </ElFormItem>
          <ElFormItem class="label-text">
            <p class="m-0">px</p>
          </ElFormItem>
          <ElFormItem class="label-empty !ml-18px">
            <ElCheckbox v-model="isKeepAspectRatio" :disabled="(aspectRatioEnable && !isCropper) || !aspectRatioEnable" @change="changeAspectRatioHandler">{{ t('infra.resource.library.file.edit.image', 'keepAspectRatio') }}</ElCheckbox>
          </ElFormItem>
        </el-form>
        <div :class="['cropper-image-wrap', isCropper ? 'cropper' : '']">
          <CropperImage v-if="cropperSource.src" :src="cropperSource.src" height="330px" :circled="false" :options="options" @cropend="handleCropper" @ready="handleReady" />
          <div v-if="isCropper" class="image-size-text">
            <el-text>{{ t('infra.resource.library.file.edit.image', 'size') }}</el-text>
            <el-text>{{ cropperSource.width }}</el-text>
            <el-text class="!mx-4px">x</el-text>
            <el-text>{{ cropperSource.height }}</el-text>
          </div>
          <div v-if="isCropper" class="btn-save" @click="saveCropper">{{ t('action', 'confirm') }}</div>
        </div>
        <div class="cropper-toolbar">
          <div class="cropper-operate">
            <ElUpload :fileList="fileList" :show-file-list="false" :accept="acceptConfig" :beforeUpload="beforeUpload" class="flex flex-1 mr-5px">
              <div :class="['item-operate primary']">
                <div class="item-icon"><SvgIcon icon="ad:upload-outlined" /></div>
                <div class="item-text">{{ t('cropper', 'uploadImage') }}</div>
              </div>
            </ElUpload>
            <div :class="['item-operate', cropperSource.src !== '' ? '' : 'disabled', currentOperate === 'reset' ? 'active' : '']" @click="handlerToolbar('reset', 'reset')">
              <div class="item-icon"><svg-icon icon="ad:reload-outlined" /></div>
              <div class="item-text">{{ t('action', 'reset') }}</div>
            </div>
            <div :class="['item-operate', cropperSource.src !== '' ? '' : 'disabled', currentOperate === 'rotateLeft' ? 'active' : '']" @click="handlerToolbar('rotateLeft', 'rotate', -45)">
              <div class="item-icon"><svg-icon icon="ad:rotate-left-outlined" /></div>
              <div class="item-text">{{ t('cropper', 'rotateLeft') }}</div>
            </div>
            <div :class="['item-operate', cropperSource.src !== '' ? '' : 'disabled', currentOperate === 'rotateRight' ? 'active' : '']" @click="handlerToolbar('rotateRight', 'rotate', 45)">
              <div class="item-icon"><svg-icon icon="ad:rotate-right-outlined" /></div>
              <div class="item-text">{{ t('cropper', 'rotateRight') }}</div>
            </div>
            <div :class="['item-operate', cropperSource.src !== '' ? '' : 'disabled', currentOperate === 'horizontalFlip' ? 'active' : '']" @click="handlerToolbar('horizontalFlip', 'scaleX')">
              <div class="item-icon"><svg-icon icon="ci:horizontal-flip" /></div>
              <div class="item-text">{{ t('cropper', 'horizontalFlip') }}</div>
            </div>
            <div :class="['item-operate', cropperSource.src !== '' ? '' : 'disabled', currentOperate === 'verticalFlip' ? 'active' : '']" @click="handlerToolbar('verticalFlip', 'scaleY')">
              <div class="item-icon"><svg-icon icon="ci:vertical-flip" /></div>
              <div class="item-text">{{ t('cropper', 'verticalFlip') }}</div>
            </div>
            <div :class="['item-operate', cropperSource.src !== '' ? '' : 'disabled', currentOperate === 'zoomIn' ? 'active' : '']" @click="handlerToolbar('zoomIn', 'zoom', 0.1)">
              <div class="item-icon"><svg-icon icon="ad:zoom-in-outlined" /></div>
              <div class="item-text">{{ t('cropper', 'zoomIn') }}</div>
            </div>
            <div :class="['item-operate', cropperSource.src !== '' ? '' : 'disabled', currentOperate === 'zoomOut' ? 'active' : '']" @click="handlerToolbar('zoomOut', 'zoom', -0.1)">
              <div class="item-icon"><svg-icon icon="ad:zoom-out-outlined" /></div>
              <div class="item-text">{{ t('cropper', 'zoomOut') }}</div>
            </div>
            <div :class="['item-operate', cropperSource.src !== '' ? '' : 'disabled', isCropper ? 'active' : '', isCropper ? 'cropper' : '']" @click="handlerCopper()">
              <div class="item-icon"><svg-icon icon="ri:crop-line" /></div>
              <div class="item-text">{{ t('cropper', 'title') }}</div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <template #footer>
      <el-button @click="visible = false">{{ t('action', 'cancel') }}</el-button>
      <el-button :disabled="isCropper" type="primary" @click="submitForm">{{ t('action', 'confirm') }}</el-button>
    </template>
  </Dialog>
</template>

<style scoped lang="scss">
.cropper-wrap {
  display: flex;
  flex-direction: row;

  .cropper-content {
    position: relative;

    .cropper-image-wrap {
      position: relative;
      height: 330px;
      background: #eee;

      &.cropper {
        background-image: linear-gradient(45deg,
            rgba(0, 0, 0, 0.25) 25%,
            transparent 0,
            transparent 75%,
            rgba(0, 0, 0, 0.25) 0),
          linear-gradient(45deg,
            rgba(0, 0, 0, 0.25) 25%,
            transparent 0,
            transparent 75%,
            rgba(0, 0, 0, 0.25) 0);
        background-position: 0 0, 12px 12px;
        background-size: 24px 24px;
      }

      .image-size-text {
        position: absolute;
        top: 10px;
        left: 10px;
        z-index: 10;
        height: 20px;
        padding: 0 5px;
        font-size: 12px;
        line-height: 20px;
        background: rgba(0, 0, 0, 0.5);
        border-radius: 2px;

        .el-text{
          color: var(--el-color-white);
        }
      }

      .btn-save {
        position: absolute;
        bottom: 10px;
        left: 50%;
        width: 98px;
        height: 26px;
        margin-left: -49px;
        font-size: 12px;
        line-height: 24px;
        color: var(--el-color-primary);
        text-align: center;
        cursor: pointer;
        background-color: #fff;
        border: 1px solid var(--el-color-primary);
        border-radius: 2px;
      }
    }
  }

  .cropper-toolbar {
    display: flex;
    flex: 1;
    align-items: center;
    justify-content: space-between;
    margin-top: 5px;

    .cropper-operate {
      display: flex;
      flex: 1;

      .item-operate {
        flex: 1;
        cursor: pointer;

        &.primary{
          .item-icon {
            color: var(--el-color-white);
            background-color: var(--el-color-primary);
          }

          .item-text {
            color: var(--el-color-primary);
          }

          &:hover {
            .item-icon {
              background-color: var(--el-color-primary);
            }
          }
        }

        &:hover {
          .item-icon {
            background-color: var(--el-color-primary-light-9);
          }
        }

        &.disabled {
          cursor: not-allowed;
        }

        &.active {
          .item-icon {
            color: var(--el-color-primary);
            background-color: var(--el-color-primary-light-9);
          }

          .item-text {
            color: var(--el-color-primary);
          }
        }

        &.cropper {
          .item-icon {
            color: var(--el-color-primary);
          }
        }

        .item-icon {
          display: flex;
          align-items: center;
          justify-content: center;
          height: 32px;
          background-color: var(--el-fill-color-light);

          .el-icon {
            font-size: 20px !important;
          }
        }

        .item-text {
          display: flex;
          align-items: center;
          justify-content: center;
          height: 16px;
          margin-top: 2px;
          font-size: 12px;
        }
      }

      .item-operate+.item-operate {
        margin-left: 5px;
      }
    }
  }
}

:deep(.el-upload) {
  flex: 1;
}
</style>
